In [1]:
import numpy as np
#import random
#import os
#import pandas as pd
import tensorflow as tf
#import matplotlib.pyplot as plt
#!pip install mitdeeplearning
#import mitdeeplearning as mdl
#!pip install -U mxnet-cu101==1.7.0

Gradient Vector - Scalar

4. Vector - Scalar.png

In [2]:
x = tf.constant([[1., 2.]])
y = tf.constant([[3., 4.]])
tf.matmul(x, y, transpose_b=True)
Out[2]:
<tf.Tensor: shape=(1, 1), dtype=float32, numpy=array([[11.]], dtype=float32)>

Whether the input is scalar or tensor, GradientTape.jacobian efficiently calculates the gradient of each element of the source with respect to each element of the target(s).

In [3]:
x = tf.constant([[1.0, 2.0]], dtype=np.float32)
y = tf.constant([[3.0, 4.0]], dtype=np.float32)
with tf.GradientTape(persistent=True) as g:
  g.watch(x)
  g.watch(y)
  z = tf.matmul(x, y, transpose_b=True)
jacobian_x = g.jacobian(z, x) # g.batch_jacobian(z, x)
jacobian_y = g.jacobian(z, y) # g.batch_jacobian(z, y)
In [4]:
print(jacobian_x)
print(jacobian_y)
tf.Tensor([[[[3. 4.]]]], shape=(1, 1, 1, 2), dtype=float32)
tf.Tensor([[[[1. 2.]]]], shape=(1, 1, 1, 2), dtype=float32)

Gradient Vector - Vector

Ví dụ 1:

$\textbf{y} = \textbf{x} \bigodot \textbf{x}$

In [5]:
x = tf.constant([[1., 2.]]) # tạo ra 1 vector
x*x
Out[5]:
<tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[1., 4.]], dtype=float32)>
In [9]:
with tf.GradientTape() as g:
  g.watch(x)
  y = x * x
jacobian_x = g.jacobian(y, x)
# gradient_x = g.gradient(y, x)
In [7]:
jacobian_x
Out[7]:
<tf.Tensor: shape=(1, 2, 1, 2), dtype=float32, numpy=
array([[[[2., 0.]],

        [[0., 4.]]]], dtype=float32)>
In [10]:
gradient_x
Out[10]:
<tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[2., 4.]], dtype=float32)>

Nx: Chiều của Jacobian là một ma trận trong khi đó chiều của Gradient là một vector

Bản chất của Jacobian là cách viết chi tiết hơn của 1 Gradient

Ví dụ 2:

1.png

1.png

In [13]:
x = tf.constant([[1., 3.]])
a = tf.constant(3.)
with tf.GradientTape() as g:
  g.watch(x)
  y = a * x

jacobian_x = g.jacobian(y, x)
# gradient_x = g.gradient(y, x)
In [12]:
jacobian_x
Out[12]:
<tf.Tensor: shape=(1, 2, 1, 2), dtype=float32, numpy=
array([[[[3., 0.]],

        [[0., 3.]]]], dtype=float32)>
In [14]:
gradient_x
Out[14]:
<tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[3., 3.]], dtype=float32)>

Jacobian là một cái thể hiện Gradient chi tiết nhất có thể

1.png

Bởi vì đạo hàm là sự ảnh hưởng của X lên Y. Vì vậy khi ta muốn tính đạo hàm của Y trên X ta phải tính đạo hàm từng phần tử trong Y với từng phàn tử trong X

1.png

Trước đây Y chỉ là một số thôi, thì Gradient của Y chỉ là: 2.png

Nhưng giờ Y là một vector rồi cho nên phải mở vector ra cho nên nó kéo dài vector Gradient thành một ma trận. Ma trận này gọi là Jacobian Matrix:

1.png

Nó thể hiện chi tiết đạo hàm của từng phần tử Y với từng phần tử X cho dù từ Matrix -> Matrix hay Tensor -> Tensor, nó sẽ tính đạo hàm của phần tử đó trên tất cả đầu vào

Quay lại Ví dụ 2:

Tính Jacobian:

1.png

Ví dụ 3:

1.png

Ta vẫn có thể chuyển Jacobian về Gradient bằng cách cộng tât cả các thành phần của 1 cột của Jacobian lại với nhau

1.png

Bản chất của những cột này là những đạo hàm riêng. Có nghĩa là những cái $x_1$ | $x_2$ này ảnh hưởng đến $y_i$ nào thì nó sẽ nằm ở đây hết. Cho nên để tính chung (Gradient) thì chúng ta chỉ cần cộng gộp cột lại

1.png

Gradient matrix - matrix

Sử dụng một ma trận nhân vào vector $x$ để ra vector $y$

APP_SCRIPT_DOCS_IMAGE2.png

==============

Ví dụ 1:

==============

1.png

In [15]:
W = tf.constant([[1., 2., 3.], 
                 [-1., 3., 2.],
                 [2., 3., 1.]])

x = tf.constant([[4.], [5.], [6.]])
tf.matmul(W, x)
Out[15]:
<tf.Tensor: shape=(3, 1), dtype=float32, numpy=
array([[32.],
       [23.],
       [29.]], dtype=float32)>
In [16]:
with tf.GradientTape(persistent=True) as g:
  g.watch(W)
  g.watch(x)
  f = tf.matmul(W, x)

gradient_dW = g.gradient(f, W)
# jacobian_dW = g.jacobian(f, W)
# gradient_dx = g.gradient(f, x)
jacobian_dx = g.jacobian(f, x)
print(gradient_dW)
WARNING:tensorflow:5 out of the last 5 calls to <function pfor.<locals>.f at 0x7f91303b9400> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has experimental_relax_shapes=True option that relaxes argument shapes that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.
tf.Tensor(
[[4. 5. 6.]
 [4. 5. 6.]
 [4. 5. 6.]], shape=(3, 3), dtype=float32)
In [17]:
print(jacobian_dx) # = W
# 1 2 3
#-1 3 2
# 2 3 1

# => gradient_dx = [[2. 8. 6.]]
tf.Tensor(
[[[[ 1.]
   [ 2.]
   [ 3.]]]


 [[[-1.]
   [ 3.]
   [ 2.]]]


 [[[ 2.]
   [ 3.]
   [ 1.]]]], shape=(3, 1, 3, 1), dtype=float32)

Phân tích (theo x):

1.png

===================================================================================================================

1.png

1.png

Phân tích (theo W):

Đạo hàm của Vector ở trên W

Ma trận -> Vector

1.png

Ques: Jacobian có chiều thế nào?

Phân tích :

  • Bản chất của Gradient là chúng ta tính tất cả sự ảnh hưởng của tất cả những phần tử trong dữ liệu của chúng ta (Ma trận thì nó là trong ma trận, Vector thì nó là trong Vector) đến kết quả. $=>$ Vì vậy Gradient của chúng ta là ($x_1$, $x_2$) thì chiều Gradient cũng là ($x_1$, $x_2$)

Còn nếu ta có ma trận $W$ (3, 3):

1.png

Và $Y$ (3, 1):

2.png

Thì ta lấy từng phần tử trong $Y$ ta đạo hàm lên $W$:

1.png

Có 3 dòng, 1 cột, mỗi 1 cái bên trong lại là ma trận 3x3

Vậy nhìn nhanh, Jacobian có chiều ($Y , W$) tức là (3, 1, 3, 3)

tải xuống.png

Ví dụ khác: Biến một ma trận $W(3, 2)$ -> Ma trận $Y(2, 4)$ thì ta cũng đi theo $Y$ trước rồi $W$ sau: (2, 4, 3, 2)

1.png

Trong mỗi phần tử (Khoanh bút chì) lại có một ma trận 3x2 nữa thì sẽ thành một Tensor cỡ (2 x 4 x 3 x 2) - Vector có 2 dòng, 4 cột, trong mỗi phần lại có một ma trận 3x2:

1.png

Chiều của Jacobian : (Chiều của kết quả x Chiều của đầu vào)

==============

Ví dụ 2:

==============

$ A = \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} $ $B = \begin{bmatrix} 5 & 6 \\ 7 & 8 \end{bmatrix} $ $ C = A \cdot B $. Tính $\nabla_{A}{C}$ và Jacobian tương ứng.

In [18]:
A = tf.constant([[1, 2], [3, 4]], dtype=np.float32) # Dự đoán cỡ Jacobian của A sẽ là (2 x 2 x 2 x 2)
B = tf.constant([[5, 6], [7, 8]], dtype=np.float32) # B tương tự
# A = tf.constant([[1., 2.], [3., 4.]]) # Dự đoán cỡ Jacobian của A sẽ là (2 x 2 x 2 x 2)
# B = tf.constant([[5., 6.], [7., 8.]]) # B tương tự
tf.matmul(A, B)
Out[18]:
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[19., 22.],
       [43., 50.]], dtype=float32)>
In [19]:
with tf.GradientTape(persistent=True) as g:
  g.watch(A)
  g.watch(B)
  C = tf.matmul(A, B)

gradient_A = g.gradient(C, A)
jacobian_A = g.jacobian(C, A)
gradient_B = g.gradient(C, B)
jacobian_B = g.jacobian(C, B)
WARNING:tensorflow:6 out of the last 6 calls to <function pfor.<locals>.f at 0x7f91303b9ea0> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has experimental_relax_shapes=True option that relaxes argument shapes that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.
WARNING:tensorflow:7 out of the last 7 calls to <function pfor.<locals>.f at 0x7f913e5f2730> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has experimental_relax_shapes=True option that relaxes argument shapes that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.
In [20]:
gradient_A
# gradient_B
Out[20]:
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[11., 15.],
       [11., 15.]], dtype=float32)>
In [21]:
jacobian_A
# jacobian_B
Out[21]:
<tf.Tensor: shape=(2, 2, 2, 2), dtype=float32, numpy=
array([[[[5., 7.],
         [0., 0.]],

        [[6., 8.],
         [0., 0.]]],


       [[[0., 0.],
         [5., 7.]],

        [[0., 0.],
         [6., 8.]]]], dtype=float32)>
In [22]:
jacobian_A.shape
# jacobian_B.shape
Out[22]:
TensorShape([2, 2, 2, 2])